package stupidMineralCollector.model;

import jnibwapi.JNIBWAPI;
import jnibwapi.model.Unit;
import jnibwapi.types.OrderType.OrderTypes;
import jnibwapi.types.UnitType;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;

import stupidMineralCollector.Constants;

/**
 * Created by rudolpst on 18.02.14.
 */
public class SCV {

	final private JNIBWAPI bwapi;
	private Unit unit;
	private Unit chosenMineralPatch = null;

	private Random r = new Random();

	private int oldOrderID;

	private PheromoneMap pheromoneMap;
	private Point gatheredMineralPosition;

	// 0 -> means the scv is pointing east. pi/2 -> pointing north
	// this variable is independent of the graphics shown in-game
	private double direction;

	public SCV(Unit unit, JNIBWAPI bwapi, HashSet<Unit> mineralFields,
			PheromoneMap pheromoneMap) {
		this.unit = unit;
		this.bwapi = bwapi;
		this.pheromoneMap = pheromoneMap;

		r = new Random();
		direction = r.nextDouble() * 2.0 * Math.PI;
	}

	public void step() throws Exception {
		ArrayList<Unit> mineralPatches = getMineralPatchesNearby();

		if (unit.isIdle()) {
			if (mineralPatches.size() > 0) {
				// The SCV is close to some minerals and will start mining
				chosenMineralPatch = mineralPatches.get(r
						.nextInt(mineralPatches.size()));
				bwapi.gather(unit.getID(), chosenMineralPatch.getID());
				gatheredMineralPosition = new Point(chosenMineralPatch.getTileX(),chosenMineralPatch.getTileY());
			} else {
				try
				{
					search();
				}
				catch(Exception e)
				{
					System.out.println(e.getMessage());
				}
			}
		} else if (unit.getOrderID() == OrderTypes.ReturnMinerals.getID()) {
			// TODO: The SCV is on its way back to the base. You should spread
			// out some pheromones here
			
			// spread pheromones depending on the distance to the mineral patch we gathered
			if( gatheredMineralPosition != null )
			{
				int tx = unit.getTileX(), ty = unit.getTileY();
				double dist = Math.sqrt( Math.pow(tx-gatheredMineralPosition.x,2) + Math.pow(ty-gatheredMineralPosition.y,2) );
				// check distance to command center = max distance
				Unit cc = getCommandCenter();
				double maxDist = Math.sqrt( Math.pow(cc.getTileX()-gatheredMineralPosition.x,2) + Math.pow(cc.getTileY()-gatheredMineralPosition.y, 2) );
				double percent = dist / maxDist;
				pheromoneMap.increasePheromones(tx,ty, percent);
			}
			else 
				pheromoneMap.increasePheromones(unit.getTileX(), unit.getTileY());
		} else if (oldOrderID == OrderTypes.ReturnMinerals.getID()
				&& unit.getOrderID() != OrderTypes.ReturnMinerals.getID()) {
			// The SCV has returned the minerals to the base and waits for new
			// instructions in the next step.
			bwapi.stop(unit.getID());
		}

		oldOrderID = unit.getOrderID();
	}

	private void search() throws Exception {
		int x = unit.getX(), y = unit.getY();
		int tx = unit.getTileX(), ty = unit.getTileY();
		
		double pheromoneValue = pheromoneMap.getScent(tx, ty);
		// check distance to command center
		Unit cc = getCommandCenter();
		double dist = Math.sqrt( Math.pow(tx-cc.getTileX(),2) + Math.pow(ty-cc.getTileY(),2) );
		
		boolean movement = false;
		int newX = x,newY = y;
		// only check pheromones if we're not next to our CC to prevent blocking
		if (dist > 10 && pheromoneValue > Constants.getInstance().getMinPheromoneLevel()
				&& pheromoneValue < Constants.getInstance()
						.getMaxPheromoneLevel()) {
			//TODO: Use the pheromones to determine the new direction
			//Notice: You have to set the minPheromoneLevel and maxPheromoneLevel properly to reach this part of the code.
			// check ph values around us and move to position with the highest value
			Point maxPos = new Point();
			double maxPh = 0;
			
			// check all neighbors for highest pheromone value
			for( int i = -1; i <= 1; ++i )
			{
				for( int j = -1; j <= 1; ++j )
				{
					// don't check our current position
					if( i == 0 && j == 0 )
						continue;
					
					int tempX = tx+i;
					int tempY = ty+j;
					// skip command center position
					if( cc.getTileX() == tempX && cc.getTileY() == tempY )
						continue;
					double ph = pheromoneMap.getScent(tempX, tempY);
					if( ph > maxPh )
					{
						maxPh = ph;
						maxPos.x = i;
						maxPos.y = j;
					}
				}
			}
			if( maxPh > 0 )
			{
				direction = getDirection(maxPos.x,maxPos.y);
				double deltaX = Math.cos(direction) * 100.0;
				double deltaY = Math.sin(direction) * 100.0;
				
				newX = (int) (x + deltaX);
				newY = (int) (y + deltaY);
				
				// check if blocked by other scv
				if( !isBlocked(newX,newY) )
					movement = true;
			}
		}
		if( !movement )
		{
			double directionChange = (r.nextDouble() * 2.0 * Math.PI / 3.0)
					- (Math.PI / 3.0);
			direction += directionChange;
			
			double deltaX = Math.cos(direction) * 100.0;
			double deltaY = Math.sin(direction) * 100.0;
			
			newX = (int) (x + deltaX);
			newY = (int) (y + deltaY);
		}
		
		bwapi.move(unit.getID(), newX, newY);
	}

	private ArrayList<Unit> getMineralPatchesNearby() {
		ArrayList<Unit> patches = new ArrayList<>();

		for (Unit mineral : bwapi.getNeutralUnits()) {
			if (mineral.getTypeID() == UnitType.UnitTypes.Resource_Mineral_Field
					.getID()) {
				double distance = Math.sqrt(Math.pow(
						mineral.getX() - unit.getX(), 2)
						+ Math.pow(mineral.getY() - unit.getY(), 2));

				if (distance < 300) {
					patches.add(mineral);
				}
			}
		}

		return patches;
	}
	
	private boolean isBlocked(int x, int y)
	{
		for( Unit u : bwapi.getAlliedUnits() )
		{
			if( u.getTypeID() == UnitType.UnitTypes.Terran_SCV.getID()
					&& x == u.getX() && y == u.getY() )
				return true;
		}
		return false;
	}
	
	private Unit getCommandCenter() throws Exception {
		for( Unit u : bwapi.getMyUnits()){
			if( u.getTypeID() == UnitType.UnitTypes.Terran_Command_Center.getID() )
				return u;
		}
		throw new Exception("No command center found!");
	}
	
	private double getDirection(int i, int j)
	{
		/*
		 * Wrong!
			-1,-1 (PI*3/4) 	0,-1 (PI/2)		1,-1 (PI/4)
			-1,0 (PI)		0,0				1,0 (0)
			-1,1 (PI*5/4)	0,1  (PI*3/2)	1,1 (PI*7/4)
			Right:
			-1,-1 (PI*5/4) 	0,-1 (PI*3/2)	1,-1 (PI*7/4)
			-1,0 (PI)		0,0				1,0 (0)
			-1,1 (PI*3/4)	0,1  (PI/2)		1,1 (PI/4)
		 */
		if( i == -1 )
		{
			if( j == -1 )	return Math.PI * 5 / 4;
			if( j ==  0 )	return Math.PI;
			/*( j ==  1 ) */return Math.PI * 3 / 4;
		}
		if( i == 1 )
		{
			if( j == -1 ) 	return Math.PI * 7 / 4;
			if( j ==  0 )	return 0;
			/*( j ==  1 ) */return Math.PI / 4;
		}
		// i == 0
		if( j == -1 )
			return Math.PI *3 / 2;
		else // j == 1
			return Math.PI / 2;
	}

	public int getID() {
		return unit.getID();
	}
	
	public int getTileX()
	{
		return unit.getTileX();
	}
	
	public int getTileY()
	{
		return unit.getTileY();
	}
}
